package icraf.carbon.gui.chart;

import icraf.carbon.core.AbacusContainer;
import icraf.carbon.core.AbacusData;
import icraf.carbon.core.CostBenefitUnit;
import icraf.carbon.core.Zone;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Random;

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;

public class AbacusChart extends ChartCanvas {
	private static final long serialVersionUID = 4253156182062973247L;

	public static void main(String[] args) {

		final JFrame frame = new JFrame();
		AbacusChart mainPanel = new AbacusChart();
		mainPanel.setPreferredSize(new Dimension(400, 300));

		mainPanel.setRangeX(-14, 612);
		mainPanel.setTitleX("C-stock");
		mainPanel.setRangeY(0, 12280);
		mainPanel.setTitleY("Cost");

		frame.getContentPane().add(mainPanel, BorderLayout.CENTER);
		frame.pack();
		frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
		SwingUtilities.invokeLater(new Runnable() {
			@Override
			public void run() {
				frame.setVisible(true);
			}
		});
	}

	private AbacusContainer abacusCont;
	private ArrayList<ShapeFormat> barLegend = new ArrayList<ShapeFormat>();
	private ArrayList<AbacusBar> barList = new ArrayList<AbacusBar>();
	private HashMap<String, ShapeFormat> colorLegend = new HashMap<String, ShapeFormat>();
	private CostBenefitUnit costBenefitUnit;
	private boolean isAreaScaleX = false;
	private boolean isShowEmission = true;
	private boolean isShowNegativeCost = true;
	private boolean isShowPositiveCost = true;
	private boolean isShowSequestration = false;
	private boolean isUpdateSymbol = true;
	private HashMap<JLabel, String> labelLegend = new HashMap<JLabel, String>();

	private FontFormat legendFormat;

	private JPanel legendPanel = new JPanel();

	public AbacusContainer getAbacusContainer() {
		return abacusCont;
	}

	public ArrayList<ShapeFormat> getBarLegend() {
		return barLegend;
	}

	ArrayList<AbacusBar> getBarList() {
		return barList;
	}

	public CostBenefitUnit getCostBenefitUnit() {
		return costBenefitUnit;
	}

	public JPanel getLegendPanel() {
		return legendPanel;
	}

	public boolean isAreaScaleX() {
		return isAreaScaleX;
	}

	private boolean isHiddenIndex(int i) {

		if (i >= abacusCont.getIndexMinEmission()) {

			if (!isShowNegativeCost
					&& abacusCont.getAbacusData(i, costBenefitUnit).getCost(
							costBenefitUnit) < 0)
				return true;
			if (!isShowPositiveCost
					&& abacusCont.getAbacusData(i, costBenefitUnit).getCost(
							costBenefitUnit) >= 0)
				return true;
		} else {
			if (!isShowNegativeCost
					&& abacusCont.getAbacusData(i, costBenefitUnit).getCost(
							costBenefitUnit) > 0)
				return true;
			if (!isShowPositiveCost
					&& abacusCont.getAbacusData(i, costBenefitUnit).getCost(
							costBenefitUnit) <= 0)
				return true;
		}
		return false;
	}

	public boolean isShowEmission() {
		return isShowEmission;
	}

	public boolean isShowNegativeNPV() {
		return isShowNegativeCost;
	}

	public boolean isShowPositiveNPV() {
		return isShowPositiveCost;
	}

	public boolean isShowSequestration() {
		return isShowSequestration;
	}

	@Override
	void paintCanvas(Graphics2D g2, int x, int y, int width, int height) {

		if (isUpdateSymbol) {
			resizeBars(g2, x, y, width, height);
			isUpdateSymbol = false;
		}

		int iStart = isShowSequestration ? isShowPositiveCost ? 0 : abacusCont
				.getIndexMaxSequestrationNegativeCost(costBenefitUnit)
				: abacusCont.getIndexMinEmission();
		int iFinish = isShowEmission ? barList.size() : abacusCont
				.getIndexMinEmission();
		for (int i = iStart; i < iFinish; i++) {
			AbacusBar b = barList.get(i);
			if (!b.isVisible())
				continue;
			if (b.getData().getEmission() == 0
					|| b.getData().getCost(costBenefitUnit) == 0)
				continue;
			g2.setColor(b.getShapeFormat().fillColor);
			g2.fill(b);
			g2.setColor(b.getShapeFormat().lineColor);
			g2.setStroke(b.getShapeFormat().generateStroke());
			g2.draw(b);
		}

		if (getMinY() != 0) {
			int pixY = getPixelY(0);
			g2.setColor(canvasSetting.xAxisExtra.lineColor);
			g2.setStroke(canvasSetting.xAxisExtra.generateStroke());
			g2.drawLine(x, pixY, x + width, pixY);
		}
		if (getMinX() != 0) {
			int pixX = getPixelX(0);
			g2.setColor(canvasSetting.xAxisExtra.lineColor);
			g2.setStroke(canvasSetting.xAxisExtra.generateStroke());
			g2.drawLine(pixX, y, pixX, y + height);
		}
	}

	private void resizeBars(Graphics2D g2, int x, int y, int width, int height) {
		double deltaX = Math.abs(this.getMaxX() - this.getMinX());
		double scaleX = width / deltaX;
		double scaleCostY = isShowNegativeCost && isShowPositiveCost ? 2 : 1; // below
																				// 0

		for (AbacusBar b : barList) {
			b.setVisible(false);
		}

		int iStart = isShowSequestration ? isShowPositiveCost ? 0 : abacusCont
				.getIndexMaxSequestrationNegativeCost(costBenefitUnit)
				: abacusCont.getIndexMinEmission();
		int iFinish = isShowEmission ? barList.size() : abacusCont
				.getIndexMinEmission();
		for (int i = iStart; i < iFinish; i++) {
			if (isHiddenIndex(i)) {
				continue;
			}
			AbacusData d = abacusCont.getAbacusData(i, costBenefitUnit);
			AbacusBar box = barList.get(i);
			box.setVisible(true);
			box.setToolTipText(d.getLabel1() + " -> " + d.getLabel2());
			box.setShapeSetting(colorLegend.get(d.getGroup()));
			if (i >= abacusCont.getIndexMinEmission()) {
				box.setEmissionBase(abacusCont.getCumulativeEmission(i - 1));
			} else {
				box.setEmissionBase(abacusCont.getCumulativeEmission(i));
			}
			double wPix = Math.abs(d.getEmission()) * scaleX;
			double xPix = ((abacusCont.getCumulativeEmission(i) - (d
					.getEmission() < 0 ? 0 : d.getEmission())) - this.getMinX())
					* scaleX;
			if (isAreaScaleX) {
				xPix = ((abacusCont.getCumulativeEmission(i) - (d.getEmission() < 0 ? 0
						: d.getEmission()))
						* abacusCont.getTotalArea() - this.getMinX())
						* scaleX;
				wPix = wPix * abacusCont.getTotalArea();
			}
			// log below 0 is negative, so should be added by max power y
			double yPix = getPixelY(d.getCost(costBenefitUnit));
			double hPix = height / scaleCostY - yPix + y;
			yPix = (d.getCost(costBenefitUnit) * Math.signum(d.getEmission()) > 0 ? yPix
					: hPix + yPix);
			if (!isShowPositiveCost)
				yPix = y;
			box.setFrame(x + xPix, yPix, wPix, hPix);
			box.setData(d);
		}
	}

	public void setAbacusContainer(AbacusContainer abacusCont,
			CostBenefitUnit defaultCBU) {
		this.abacusCont = abacusCont;
		this.setCostBenefitUnit(defaultCBU);
		updateAxisScale();
	}

	public void setAreaScaleX(boolean isAreaScaleX) {
		this.isAreaScaleX = isAreaScaleX;
		if (costBenefitUnit == null)
			return;
		updateAxisScale();
	}

	public void setBarLegend(ArrayList<ShapeFormat> barLegend) {
		this.barLegend = barLegend;
		updateChartFormat();
		repaint();
	}

	public void setCostBenefitUnit(CostBenefitUnit costBenefitUnit) {
		this.costBenefitUnit = costBenefitUnit;
	}

	public void setLegendFormat(FontFormat format) {
		legendFormat = format;
	}

	public void setShowEmission(boolean isShowEmission) {
		this.isShowEmission = isShowEmission;
		if (costBenefitUnit == null)
			return;
		updateAxisScale();
		updateBarSize();
	}

	public void setShowNegativeNPV(boolean isShowNegativeNPV) {
		this.isShowNegativeCost = isShowNegativeNPV;
		if (costBenefitUnit == null)
			return;
		updateAxisScale();
		updateBarSize();
	}

	public void setShowPositiveNPV(boolean isShowPositiveNPV) {
		this.isShowPositiveCost = isShowPositiveNPV;
		if (costBenefitUnit == null)
			return;
		updateAxisScale();
		updateBarSize();
	}

	public void setShowSequestration(boolean isShowSequestration) {
		this.isShowSequestration = isShowSequestration;
		if (costBenefitUnit == null)
			return;
		updateAxisScale();
		updateBarSize();
	}

	private void updateAxisScale() {
		double xMin, xMax, yMin, yMax;
		if (isShowSequestration) {
			if (isShowPositiveCost) {
				// xMin = emissionCum[0];
				xMin = abacusCont.getTotalSequestration();
			} else {
				// xMin = emissionCum[indexMinNegativeNPV];
				xMin = abacusCont
						.getTotalSequestrationNegativeCost(costBenefitUnit);
			}
		} else {
			xMin = 0;
		}
		if (isShowEmission) {
			// xMax = emissionCum[indexMaxNegativeNPV];
			xMax = abacusCont.getTotalEmissionNegativeCost(costBenefitUnit);
			if (isShowPositiveCost) {
				// xMax = emissionCum[emissionCum.length - 1];
				xMax = abacusCont.getTotalEmission();
			}

			if (isShowSequestration) {
				yMin = 0;
				yMax = 0;
				if (isShowPositiveCost) {
					yMax = Math.max(
							abacusCont.getDataMaxEmission(costBenefitUnit)
									.getCost(costBenefitUnit),
							-abacusCont.getAbacusData(0, costBenefitUnit)
									.getCost(costBenefitUnit));
				}
				if (isShowNegativeCost) {
					yMin = Math.min(
							abacusCont.getDataMinEmission(costBenefitUnit)
									.getCost(costBenefitUnit), -abacusCont
									.getDataMinSequestration(costBenefitUnit)
									.getCost(costBenefitUnit));
				}
			} else {
				yMin = 0;
				yMax = 0;
				if (isShowPositiveCost) {
					yMax = abacusCont.getDataMaxEmission(costBenefitUnit)
							.getCost(costBenefitUnit);
				}
				if (isShowNegativeCost) {
					yMin = abacusCont.getDataMinEmission(costBenefitUnit)
							.getCost(costBenefitUnit);
				}

			}
		} else {
			xMax = 0;
			yMin = 0;
			yMax = 0;
			if (isShowPositiveCost)
				yMax = Math.abs(abacusCont.getDataMaxSequestration(
						costBenefitUnit).getCost(costBenefitUnit));
			if (isShowNegativeCost)
				yMin = -abacusCont.getDataMinSequestration(costBenefitUnit)
						.getCost(costBenefitUnit);
		}
		if (isAreaScaleX) {
			this.setRangeX(xMin * abacusCont.getTotalArea(),
					xMax * abacusCont.getTotalArea());
		} else {
			this.setRangeX(xMin, xMax);
		}
		if (isShowNegativeCost && isShowPositiveCost) {
			double d = Math.max(Math.abs(yMin), yMax);
			this.setRangeY(-d, d);
		} else {
			this.setRangeY(yMin, yMax);
		}
		this.setShowNegativeLogY(isShowNegativeCost);
		this.setShowPositiveY(isShowPositiveCost);
	}

	public void updateBarData() {
		int n = 0;
		n = abacusCont.getDataSize(costBenefitUnit) - barList.size();
		if (n < 0) {
			n = Math.abs(n);
			for (int i = 0; i < n; i++) {
				barList.remove(barList.size() - 1);
			}
		} else {
			for (int i = 0; i < n; i++) {
				AbacusBar box = new AbacusBar();
				barList.add(box);
			}
		}
		updateChartFormat();
	}

	public void updateBarSize() {
		isUpdateSymbol = true;
		this.repaint();
	}

	public void updateChartFormat() {
		int defColorIdx = 0;
		labelLegend.clear();
		legendPanel.removeAll();
		colorLegend.clear();
		int w = 0;
		int fontH = (legendFormat == null) ? 20 : getFontMetrics(
				legendFormat.font).getHeight();
		if (abacusCont != null && abacusCont.getDataSize(costBenefitUnit) > 0) {
			Random r = new Random(69);
			ArrayList<Zone> zones = abacusCont.getDataZoneList();
			legendPanel.setLayout(new GridLayout(zones.size(), 1, 0, 2));
			for (int i = 0; i < zones.size(); i++) {
				if (colorLegend.containsKey(zones.get(i).getLabel()))
					continue;
				ShapeFormat ss = null;
				if (defColorIdx < barLegend.size()) {
					ss = barLegend.get(defColorIdx);
					defColorIdx++;
				} else {
					Color c = new Color(r.nextInt(150), 55 + r.nextInt(200),
							55 + r.nextInt(150));
					ss = new ShapeFormat();
					ss.fillColor = c;
				}
				colorLegend.put(zones.get(i).getLabel(), ss);
				JLabel l = new JLabel(zones.get(i).getLabel(), new ColorIcon(
						ss.fillColor, fontH / 2, fontH / 2), SwingConstants.LEFT);
				labelLegend.put(l, zones.get(i).getLabel());
				l.setFont(legendFormat.font);
				l.setForeground(legendFormat.fontColor);
				//l.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
				w = Math.max(w,
						l.getFontMetrics(l.getFont()).stringWidth(l.getText()));
				legendPanel.add(l);
			}
		}
		int h = legendPanel.getComponentCount()	* fontH+4;
		legendPanel.setSize(w + fontH / 2 + 20, h);
		isUpdateSymbol = true;
		
		this.repaint();
	}

	public FontFormat getLegendFormat() {
		return legendFormat;
	}

}
